﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.

// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	:   FI.Net Application Framework 
// COMPONENT ID :   FI.EntityFramework
// COMPONENT DESC:  
//
// CREATED DATE/BY: 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY:	DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================

#endregion

using FI.Core.Properties;
using FI.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace FI.Core
{
    /// <summary>
    /// Define an attribute used for entity, indicate the property is binding property.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
    public sealed class DataPropertyAttribute : Attribute
    {
        private static readonly Dictionary<Type, IList<DataPropertyInfo>> _entityPropertyNames = new Dictionary<Type, IList<DataPropertyInfo>>();
        private static object syncHelper = new object();
        /// <summary>
        /// The property name.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Initializes with a specified name.
        /// </summary>
        /// <param name="name"></param>
        public DataPropertyAttribute(string name)
        {
            Guard.ArgumentNotNullOrEmpty(name, "name");
            this.Name = name;
        }

        /// <summary>
        /// Get all PropertyInfo of the specified entity.
        /// </summary>
        /// <param name="entity">The specified entity.</param>
        /// <returns>The collection of PropertyInfo.</returns>
        public static IList<string> GetDataPropertyNames(object entity)
        {
            Guard.ArgumentNotNull(entity, "entity");

            if (_entityPropertyNames.ContainsKey(entity.GetType()))
            {
                return _entityPropertyNames[entity.GetType()].Select(info=>info.Name).ToList();
            }

            lock (syncHelper)
            {
                if (!_entityPropertyNames.ContainsKey(entity.GetType()))
                {
                    AddEntityPropertyNames(entity.GetType());
                }
            }
            return _entityPropertyNames[entity.GetType()].Select(info => info.Name).ToList();
        }

        /// <summary>
        /// Gets the name of the entity property.
        /// </summary>
        /// <param name="entityType">Type of the entity.</param>
        /// <param name="dataPropertyName">Name of the data proeprty.</param>
        /// <returns></returns>
        public static string GetEntityPropertyName(Type entityType, string dataPropertyName)
        {
            Guard.ArgumentNotNull(entityType, "entityType");
            Guard.ArgumentNotNullOrEmpty(dataPropertyName, "dataPropertyName");

            if (_entityPropertyNames.ContainsKey(entityType))
            {
                return _entityPropertyNames[entityType].First(item => item.Name == dataPropertyName).ProeprtyName;
            }

            lock (syncHelper)
            {
                if (!_entityPropertyNames.ContainsKey(entityType))
                {
                    AddEntityPropertyNames(entityType);
                }
            }
            return _entityPropertyNames[entityType].First(item => item.Name == dataPropertyName).ProeprtyName;
        }

        private static void AddEntityPropertyNames(Type type)
        {
            var typedProeprties = from property in type.GetProperties()
                                  where property.GetCustomAttributes(true).Any(attribute => attribute is DataPropertyAttribute)
                                  select new DataPropertyInfo
                                  {
                                      Name = ((DataPropertyAttribute)property.GetCustomAttributes(typeof(DataPropertyAttribute), true)[0]).Name,
                                      ProeprtyName = property.Name
                                  };

            var proeprties = from proeprty in typedProeprties
                             group proeprty by proeprty.Name into nameGroup
                             where nameGroup.Count() >= 2
                             select nameGroup.Key;
            if (proeprties.Any())
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.ExceptionDataPropertyDuplicateName, proeprties.First()));
            }
            _entityPropertyNames[type] = typedProeprties.ToList();
        }
    }

    internal class DataPropertyInfo
    {
        public string Name { get;  set; }
        public string ProeprtyName { get;  set; }
    }
}